package goldgame;

import java.awt.event.*;
import java.util.*;

/** 
 * The controller class of the framework. 
 * Listens to user keystrokes and 
 * <ul>
 * <li> quits the application if the keystroke is 'Q'.
 * <li> starts a new game if the keystroke is one of those registered in
 *      GameFactory.
 * <li> passes it on to the current game otherwise.
 * </ul>
 */
public class GameController implements Runnable {

	/** The view this controller is connected to. */
	private GameView view;

	/** The game model describes the running game. */
	private GameModel gameModel;

	/** The timeout interval between each update. (millis) */
	private int updateInterval;

	/** True when game is running. */
	private boolean isRunning;

	/** Listener for key events to the game. */
	private KeyListener keyListener;

        /** A queue for all keypresses which so far haven't been processed */
        private java.util.List<Integer> keypresses;
	/* The declaration of keypresses above uses the language feature 'generic types'.
	 * It can be declared in the old way like this:
	 *    private java.util.List keypresses
	 * This will however result in a warning at compilation
	 * "Integer" in this case is the type of the objects that are going 
	 * to be used in the List
	 */

	/** The thread which the game runs in. */
	private Thread gameThread;

	
	/**
	 * Creats a new GameContoller associated with supplied view.
	 */
	public GameController(GameView view) {
		this.view = view;
		this.gameModel = null;
		this.isRunning = false;
		this.updateInterval = 150;

		this.keypresses = new LinkedList<Integer>();
		/* The object creation above uses the language feature 'generic types'.
		 * It can be declared in the old way like this:
		 *    this.keypresses = new LinkedList();
		 * This will however result in a warning at compilation
		 * "Integer" in this case is the type of the objects that are going 
		 * to be used in the List
		 */

		this.gameThread = null;

		// Create the key listener which will listen for gamekeys
		this.keyListener = 
			new KeyAdapter() {
				public void keyPressed(KeyEvent event) {
					enqueueKeyPress( event.getKeyCode() );
				}
			};
			   
	}

	/**
	 * Add a key press to the end of the queue
	 */
	private synchronized void enqueueKeyPress(int key) 
	{
	        keypresses.add(new Integer(key));
	}

	/**
	 * Get a key press, and remove it from the queue. Returns 0 if no key press is available.
	 * 
	 *
	 * @return 0 or next unprocessed key press.
	 */
	private synchronized int nextKeyPress() {
	    	if(keypresses.isEmpty())
	    		return 0;
	    	else
	    		return ((Integer) keypresses.remove(0)).intValue();
	}


	/**
	 * Starts a new game.
	 *
	 * @param gameModel Game to start
	 */
	public void startGame(GameModel gameModel) {
		if(isRunning) {
			throw new IllegalStateException("Game is already running");
		}



		// Start listening for key events
		view.addKeyListener(keyListener);
	
		// Tell the view what to paint...
		view.setModel(gameModel);

		// Actually start the game
		this.gameModel = gameModel;
		isRunning = true;

		// Create the new thread and start it...
		gameThread = new Thread(this);
		gameThread.start();
	}

    /**
     * Stops the currently running game, if any.
	 */
	public void stopGame() {
		// Setting isRunning to false will
		// make the thread stop (see run())
		isRunning = false;

		// Unset the game model...
		view.setModel(null);

		// Stop listening for events
		view.removeKeyListener(keyListener);

		// Make sure we wait til the thread has stopped...
		if (gameThread !=  null) {
			while (gameThread.isAlive()) {
				try { 
					Thread.sleep(100);
				} catch (InterruptedException ie) {
					// do nasing...
				}
			}
		}
	}

	
	/**
	 * This code runs the game in a different thread.
	 */
	public void run() {

		while(isRunning) {
			try {
				// Tell model to update, send next key press.
				// or 0 if no new keypress since last update.
			    gameModel.gameUpdate(nextKeyPress());
				
				view.repaint();

				Thread.sleep(updateInterval);
			}
			catch (GameOverException e) {
				// we got a game over signal, time to exit...
			        // The current implementation ignores the game score
				isRunning = false;
			}
			catch (InterruptedException e) {
				// if we get this exception, we're asked to terminate ourselves
				isRunning = false;
			}
		}
	}
}
